<?php
/***
 * Candy框架 框架模板引擎类
 * 
 * $Author: 刘森 (fingerboy@qq.com) $
 * $Date: 2019-08-01 23:42:51 $   
 */
 
declare(strict_types=1);
namespace Candy\Core;

defined('CANDY') OR die('You Are A Bad Guy. o_O???');

Class View {
	/**
	 * 构造方法，用于初使化成员属性
	 */
	public function __construct(){
		//控制器和操作
		$this->control = ucfirst(strtolower($_GET['m']));     //默认控制器
		$this->action = $_GET['a'];     //默认操作
		
		if(is_null(C('API'))){
			$this->leftDelimiter = is_null(G('LEFTDELIMITER')) ? '<{' : G('LEFTDELIMITER');   //模板文件中使用的“左”分隔符号
			$this->rightDelimiter = is_null(G('RIGHTDELIMITER')) ? '}>' : G('RIGHTDELIMITER');   //模板文件中使用的“右”分隔符号
			$this->templateDir = is_null(C('TEMPLATE_DIR')) ? CANDYROOT : C('TEMPLATE_DIR');
			$this->compileDir = C('COMPOLE_DIR');    //里的文件是自动生成的，合成的文件
			$this->cacheDir = C('CACHE_DIR');  //设置缓存的目录
			$this->tplSuffix = is_null(G('TPLPREFIX')) ? '.html' : G('TPLPREFIX');    //模板后缀
			$this->tplSeparate = is_null(G('SEPARATE')) ? '/' : G('SEPARATE');    //模板目录分隔符
			$this->view_vars = [];   //模板变量数组
			$this->foreachParameter = [];   //foreach模块参数
			$this->extend = CANDYPATH .'Vendor/View/';   //模板引擎扩展路径
			$this->ref = [];   //模板全局变量组
			$this->outPutFilter = ['beforeHtmlShow'];   //模板过来方法
		}
	}
	
	/**
     * 执行显示模板文件
     *
     * @param string $name
     * @param string $templateDir
     * @param string $cacheId
     */
	public function display(string $name=null,string $cacheId = '',bool $single = false,string $templateDir = null): void
	{
		if(is_null(C('API'))){
			$this->makeContent($name, $cacheId, $single, $templateDir);
			$fomat = 'html';
		}else{
			//API模式
			$fomat = C('API');
			
			//直接返回
			if(isset($tmp['apidata'])){
				$this->content = $data;
			}else{
				//整理返回数据格式
				$tmp = $this->data2api('');
				if(count($tmp)>0){
					if(isset($tmp['apidata'])){
						$data = $tmp['apidata'];
					}elseif(isset($tmp['data'])){
						$data = $tmp;
					}else{
						//状态码
						if(isset($tmp['status'])){
							$data['status'] = $tmp['status'];
							unset($tmp['status']);
						}else{
							if(isset($tmp['code'])){
								$data['code'] = $tmp['code'];
								unset($tmp['code']);
							}else{
								//默认状态码 1 表示请求成功
								$data['status'] = 1;
							}
						}
						//请求信息
						if(isset($tmp['message'])){
							$data['msg'] = $tmp['message'];
							unset($tmp['message']);
						}
						
						//其他信息打包进data
						if(count($tmp)>0){
							$data['data'] = $tmp;
						}
					}
				}else{
					$data['status'] = -1;
					$data['msg'] = 'nodata';
				}
				//兼容workerman 补充sessionid
				if(defined('CLIWORKING')){
					$data['sessionid'] = \Candy\Core\Container::getInstance()->get('Request')->sessionId();
				}else{
					$data['sessionid'] = session_id();
				}
				
				$this->content = $data;
			}
		}
		N('Respond')::create('', $fomat, 200)->send();
	}
	
	/**
     * 执行显示模板文件
     *
     * @param string $name
     * @param string $templateDir
     * @param string $cacheId
     */
	public function makeContent(string $name=null,string $cacheId = '',bool $single = false,string $templateDir = null): void
	{
		//跨项目调用
		if(!empty($name) && stripos($name, ':')){
			[$oappname, $name] = explode(':', $name, 2);
			
			//重置目录
			initTplPath($oappname);
			$this->templateDir = is_null($templateDir) ? (is_null(C('TEMPLATE_DIR')) ? CANDYROOT : C('TEMPLATE_DIR')) : $templateDir;
			$this->compileDir = C('COMPOLE_DIR');    //里的文件是自动生成的，合成的文件
			$this->cacheDir = C('CACHE_DIR');  //设置缓存的目录
		}
		
		//基础变量
		$this->tplName = '';    //模板目录分隔符
		$this->caching = G('CSTART');     //设置缓存开启
		$this->cacheLifeTime = G('CTIME'); //设置缓存的时间 
		
		//将部分全局变量直接分配到模板中使用	
		$this->view_vars['lang'] = L();
		$this->view_vars['root'] = G('C_ROOT');
		$this->view_vars['app'] = C('APPNAME');
		$this->view_vars['public'] = G('C_PUBLIC');
		$this->view_vars['res'] = C('C_RES');
		
		//框架变量
		$this->view_vars['candy_ver'] = CANDY_VERSION;
		$this->view_vars['candy_url'] = CANDY_URL;
		//运行模式输出
		if(defined('CLIWORKING'))
			$this->view_vars['cli'] = true;
		
		//浏览模式输出
		if(isMobile())
			$this->view_vars['ismobile'] = true;
		
		//文件名称检查
		if(is_null($name)){
			$name=ucfirst($this->control). $this->tplSeparate . $this->action;
		}elseif(str_contains($name,'/')){
			$name=ucfirst(str_replace('/', $this->tplSeparate, $name));
		}else{
			$name=ucfirst($this->control). $this->tplSeparate .$name;
		}
		
		//缓存路径
		$this->cachPath($cacheId);
		
		//模板输出
		$content = $this->compileFile($name, $cacheId, $single, $this->templateDir, $this->compileDir);
		
		//预置最后处理方法
		foreach($this->outPutFilter as $func){
			if(function_exists($func)){
				$content = $func($content);
			}
		}
		
		//模板路径
		$tmpfile = str_replace(CANDYROOT, '/', $this->templatePath);
		if(is_null(C('ISADDON'))){
			Debug::addMsg('当前使用的模板路径: <b> '. $tmpfile . ' </b>.', 6);
		}else{
			Debug::addMsg('当前使用' . C('APPNAME') . '插件的模板路径: <b>'. $tmpfile . '</b>.', 6);
		}
		
		//运行输出之前中间件
		$this->content = Security::runMiddlewares('Over', $content);
		
		//生成缓存
		if($this->caching && isDebug() === false){
			$this->compileFile($name, $cacheId, $single, $this->templateDir, $this->compileDir, true);
		}
		
		//运行输出之后中间件
		Security::runMiddlewares('Finish');
		
	}
	
	/*
	 * 模板变量分配
	 *
	 * @param	mixd	$key	键名或数组
	 * @param	mixed	$value	键值
	 */
	public function assign($args, $value = null,bool $apidata = true,string $call_func = ''): void
	{
		$noassign = ['foreach','view','define','const','request','server','get','post','cli','ismobile'];
		if(!is_array($args)){
			$args = [$args=>$value]; 
        }
		
		foreach($args as $key => $value){
            if($key != ''){
				if(in_array($key, $noassign)){
					Debug::addMsg('<font color="red">变量名错误 </font>assign禁止使用系统保留变量名：<b>'. implode(',', $noassign) .'</b>', 4);
				}else{
					if($apidata){
						$this->data2api($key, $value, $call_func);
					}
					$this->view_vars[$key] = $value;
				}
            }
        }
	}
	
	/*
	 * 模板编译方法
	 *
	 * @param	string	$cacheId	缓存id
	 */
	public function compileFile(string $template_name,string $cacheId = '',bool $single = false,string $templateDir = null,string $compile_dir = '',bool $nocache = false): string
	{
		//编译文件的完整路劲
		$this->compilePath($template_name, $compile_dir, $cacheId);
		
		//模板名称
		$this->templateExist($template_name, $single);
		
		//模板全局变量
		if(!empty($this->ref)){
			foreach($this->ref as $key=>$value){
				$$key = $value;
			}
		}
		
		//缓存检测 调试模式不生成缓存
		if($this->caching && isDebug() === false && $this->isCached($template_name, $templateDir, $cacheId)){
			if($nocache){
				return '';
			}else{
				ob_start();
				include $this->cachePath;
				$contents = ob_get_contents();
				ob_end_clean();
				return $contents;
			}
		}else{
			if(!$this->checkCompiled($this->templatePath, $this->compilePath)){
				$_contents = $this->templateRead($this->templatePath);//读取模板的内容
				$contents = $this->compileContent($_contents);//内容编译
				
				//创建目录
				is_dir($this->compileDir) OR \Candy\Extend\Dir::create($this->compileDir);
				
				$this->templateWrite($this->compilePath,$contents);
			}
			if($nocache){
				$_contents = $this->templateRead($this->templatePath);//读取模板的内容
				$nocache_tmp = $this->compileContent($_contents, true);//内容编译
				ob_start();
				include('code://'.$nocache_tmp);
				$_nocache = ob_get_contents();
				ob_end_clean();
				$_nocache = $this->compileContent($_nocache);//内容编译
				
				//创建缓存目录
				is_dir($this->cacheDir) OR \Candy\Extend\Dir::create($this->compileDir);
				
				$this->templateWrite($this->cachePath,$_nocache);
				return '';
			}else{
				$contents = '';
				if(file_exists($this->compilePath)){
					ob_start();
					include $this->compilePath;
					$contents = ob_get_contents();
					ob_end_clean();
				}
				return $contents;
			}
		}
	}
	
	/*
	 * 模板名称
	 *
	 * @param	string	$template_name	模板变量名
	 * @param	string	$templateDir	模板路径
	 */
	public function templateName(string $name = null,bool $single = false): void
	{
		if(!$single || is_null($name)){
			if(is_null($name)){
				$name = ucfirst($this->control). $this->tplSeparate . $this->action;
			}elseif(str_contains($name, '/')){
				$name = ucfirst(str_replace('/', $this->tplSeparate, $name));
			}else{
				$name = ucfirst($this->control). $this->tplSeparate .$name;
			}	
		}
		$name .= $this->tplSuffix;
		
		$this->tplName = $name;
	}
	
	/*
	 * 模板路径
	 */
	public function templatePath(): string
	{
		$app = C('RAPPNAME');
		$basePath = PLUGPATH . $app;
		$extendkey = 'extendpath.' . $app . $this->control;
		$extendinfo = C($extendkey);
		
		$tmppath = empty($extendinfo) ? $basePath : $extendinfo['path'];
		$tpl = $tmppath . '/Views/' . ($tmppath == $basePath ? '' : $app . '/') . C('TPLSTYLE') . C('LASTPATH') . '/' . $this->tplName;
		if(!empty($extendinfo) || file_exists($tpl)){
			$this->templatePath =  $tpl;
			$this->assign('extendres', str_replace(CANDYROOT, '/', $tmppath) . '/Views/' . ($tmppath == $basePath ? '' : $app . '/') . C('TPLSTYLE') . C('LASTPATH') . '/Resource');
		}else{
			$this->templatePath =  $this->templateDir. '/'. $this->tplName;
		}
		
		return $this->templatePath;
	}
	
	/*
	 * 模板缓存路径
	 *
	 * @param	string	$template_name	模板名称
	 * @param	string	$cacheId	缓存ID
	 */
	public function cachPath(string $cacheId): void
	{
		//缓存路径
		if(G('CSTART')){
			//纯静态
			[$uri,] = explode('?', $_SERVER['REQUEST_URI'], 2);
			stripos($uri, '.php') && [,$uri] = explode('.php', $uri, 2);
			(empty($uri) || $uri == '/') && $uri = '/index.html';
			$this->cachePath = CANDYROOT . ltrim($uri, '/');
		}else{
			$sign = implode('',$_GET);
			if(empty($cacheId)){
				$this->cachePath = $this->cacheDir . '/c_' . md5(C('APPNAME'). $this->tplName .$sign).'.php';
			}else{
				$this->cachePath = $this->cacheDir . '/c_' . md5(C('APPNAME'). $this->tplName .$sign.$cacheId).'.php';
			}
		}
	}
	
	/*
	 * 模板编译名称
	 *
	 * @param	string	$template_name	模板名称
	 */
	public function compilePath(string $template_name,string $compile_dir = '',string $cacheId = ''): void
	{
		//缓存标识
		$cache = '';
		if(!empty($cacheId)){
			$cache = '_' . md5($cacheId);
		}
		
		//缓存名称
		$new_compile_name = '%'. md5(urlencode($template_name) . $cache) .'.php';
		
		if($compile_dir != ''){
			$this->compilePath = $compile_dir.'/'.$new_compile_name;
		}else{
			$this->compilePath =  $this->compileDir.'/'.$new_compile_name;
		}
	}
	
	/*
	 * 检查模板缓存
	 *
	 * @param	bool	$back	缓存状态
	 */
	public function isCached(string $template_name ,string $templateDir,string $cacheId=''): bool
	{
		//检查缓存
		if(file_exists($this->cachePath) && (filemtime($this->cachePath) + $this->cacheLifeTime > time())){
			return true;
		}else{
			return false;
		}
	}
	
	/*
	 * 检查编译时间
	 *
	 * @param	string	$templatePath	模板路径
	 * @param	string	$compilePath	缓存路径
	 * @param	bool	是否更新状态
	 */
	public function checkCompiled(string $templatePath,string $compilePath): bool
	{
		if(file_exists($templatePath) && file_exists($compilePath)){
			if(filemtime($templatePath) <= filemtime($compilePath)){
				return true;
			}else{
				return false;
			}
		}else{
			return false;
		}	
	}
	
	/*
	 * 读取模板内容
	 *
	 * @param	string	$filename	模板路径
	 */
	public function templateRead(string $filename)
	{
        if(file_exists($filename) && is_readable($filename) && ($fd = @fopen($filename, 'rb'))){
            $contents = fileRead($filename);
			
			//处理钩子
			$contents = preg_replace_callback('#<!--\{hook\s+([^}]+)\}-->#is', 'Candy\Core\Hook::processHookCallback', $contents);
			
			//处理继承
			$search = '~'. preg_quote($this->leftDelimiter, '~') ."\s*extend(.*?)=['\"](.*?)['\"](.*?)\s*". preg_quote($this->rightDelimiter, '~') .'~s';
			preg_match_all($search, $contents, $_match);
			
			//循环处理
			while(count($_match[1]) > 0){
				foreach($_match[1] as $key=>$match){
					if(trim($match) != 'name'){
						//置换为空 防止死循环
						$contents = str_replace($_match[0][$key], '', $contents);
						continue;
					}
					
					//读取模版
					$_file = $_match[2][$key];
					$fileName = str_replace('/', $this->tplSeparate, $_file);
					$filePath =  $this->templateDir. '/'. $fileName . $this->tplSuffix;
					if(file_exists($filePath)){
						$fileContent = fileRead($filePath);
					}else{
						$fileContent = '';
					}
					$contents = str_replace($_match[0][$key], $fileContent, $contents);
				}
				preg_match_all($search, $contents, $_match);
			}
			
			//处理注释类变量标签
			$contents = str_replace('<!--'. $this->leftDelimiter,$this->leftDelimiter,$contents);
			$contents = str_replace($this->rightDelimiter .'-->',$this->rightDelimiter,$contents);
            
			//检查是否带有php代码 如果有则拒绝解析
			if(stripos($contents, '<?php') === false && stripos($contents, '<?') === false && stripos($contents, '?>') === false){
			    return $contents;
			}else{
			    return '模板文件内禁止插入php代码！';
			}
        }else{
            return false;
        }
    }
	
	/*
	 * 创建模板
	 *
	 * @param	string	$compilePath	模板路径
	 * @param	string	$contents	模板内容
	 * @param	bool	创建结果
	 */
	public function templateWrite(string $compilePath,string $contents): bool
	{
		if(empty($contents)) return false;
	    $contents = '<?php /* CandyFrame Version '. CANDY_VERSION .', Created on '. date('Y-m-d H:i:s', gmTime()) .' Compiled from '. $this->tplName .' */ ?>'. PHP_EOL . $contents;
		if(!(fileWrite($compilePath, $contents))){
			Debug::addMsg('<font color="red">系统无法写入文件'.$_tmp_file.'。</font>', 4);
			return false;
        }
		return true;
	}
	
	/*
	 * 编译模板
	 *
	 * @param	string	$content	模板内容
	 */
	public function compileContent(string $content,bool $nocache = false): string
	{
		if($content == ''){
			return '';
		}
		$left_tag = preg_quote($this->leftDelimiter, '~');
        $right_tag = preg_quote($this->rightDelimiter, '~');
		$text_blocks = [];
		$compiled_tags = [];
		$block_tags = [];
		$block_names = [];
		
		/* 处理block */
		$search = "~({$left_tag}\s*block(.*?)=['\"](.*?)['\"](.*?)\s*{$right_tag})(.*?)({$left_tag}\s*/block\s*{$right_tag})~s";
		preg_match_all($search, $content, $__match);
		if(count($__match[2]) > 0){
			foreach($__match[2] as $key=>$match){
				if(trim($match) != 'name'){
					//置换为空 防止死循环
					$content = str_replace($__match[0][$key], '', $content);
					continue;
				}
				$block_names[] = 'viewblock'. trim($__match[3][$key]);
				if(empty($__match[5][$key])){
					$___m = 'viewblock'. trim($__match[3][$key]);
					
				}else{
					if(trim($__match[4][$key]) == 'default'){
						$___m = 'viewblock'. trim($__match[3][$key]);
					}else{
						$___m = '';
					}
					$block_tags[trim($__match[3][$key])] = $__match[5][$key];
				}
				$content = str_replace($__match[0][$key], $___m, $content);
			}
		}
		
		if(count($block_tags) > 0){
			foreach($block_tags as $key=>$block){
				$content = str_replace('viewblock'.$key, $block, $content);
			}
		}
		
		if(count($block_names) > 0){
			foreach($block_names as $name){
				$content = str_replace($name, '', $content);
			}
		}
		
        /* 处理系统函数 :  :: */
		$search = "~({$left_tag}\s*:(.*?)\s*{$right_tag})~s";
		preg_match_all($search, $content, $__match);
		if(count($__match[2]) > 0){
			foreach($__match[2] as $key=>$_func){
				$nameStr = '';
				if($_func[0] == ':'){
					$newStr = '<?php ';
					$_func = substr($_func, 1);
				}else{
					$newStr = '<?php echo ';
				}
				if(stripos($_func, '$') === false){
					$newStr .= $_func .'; ?>';
				}else{
					$strArr = explode(',', $_func);
					foreach($strArr as $str){
						if(stripos($str, '$') === false){
							$newStr .= $str . ',';
						}else{
							$str = trim($str);
							$left = substr_count($str,'[');
							$right = substr_count($str,']');
							$num = $right - $left;
							if($num > 0){
								$start = strripos($str, ']');
								$start -= $num - 1;
								$str = substr($str, 0, $start) . $this->rightDelimiter . substr($str, $start);
							}else{
								if($right > 0){
									$start = stripos($str, ']');
									$str = substr($str, 0, $start) . $this->rightDelimiter . substr($str, $start);
								}else{
									$str .= $this->rightDelimiter;
								}
							}
							$newStr .= str_replace('$', $this->leftDelimiter . '$_func_fix.', $str) . ',';
						}
					}
					$newStr = trim($newStr, ',') . '; ?>';
				}
				$content = str_replace($__match[0][$key], $newStr, $content);
			}
		}
		
        /* 处理literal */
		$search = "~({$left_tag}\s*literal\s*{$right_tag})(.*?)({$left_tag}\s*/literal\s*{$right_tag})~s";
		preg_match_all($search, $content, $__match);
		if(count($__match[2]) > 0)
			foreach($__match[2] as $key => $value){
				$_m = 'viewliteral'.$key;
				$$_m = $value;
				$content = str_replace($value,$_m,$content);
			}
		
        /* 处理php */
		//注销php代码解析
		//$search = "~({$left_tag}\s*php\s*{$right_tag})(.*?)({$left_tag}\s*/php\s*{$right_tag})~s";
		//preg_match_all($search, $content, $__match);
		//if(count($__match[2]) > 0)
		//	foreach($__match[2] as $key => $value){
		//		$_m = 'viewphp'.$key;
		//		$$_m = $value;
		//		$content = str_replace($value,$_m,$content);
		//	}
		//
        /* 处理nocache */
		$nocachetmp = $content;
		$search = "~({$left_tag}\s*nocache\s*{$right_tag})(.*?)({$left_tag}\s*/nocache\s*{$right_tag})~s";
		preg_match_all($search, $nocachetmp, $__match);
		if(count($__match[2]) > 0)
			foreach($__match[2] as $key => $value){
				$_m = 'viewnocache'.$key;
				$$_m = $value;
				$nocachetmp = str_replace($value,$_m,$nocachetmp);
			}
		
		if($nocache){
			preg_match_all("~{$left_tag}\s*(.*?)\s*{$right_tag}~s", $nocachetmp, $_match);
			$templateTags = $_match[1];
			$text_blocks = preg_split("~{$left_tag}.*?{$right_tag}~s", $nocachetmp);
		}else{
			preg_match_all("~{$left_tag}\s*(.*?)\s*{$right_tag}~s", $content, $_match);
			$templateTags = $_match[1];
			$text_blocks = preg_split("~{$left_tag}.*?{$right_tag}~s", $content);
		}
		
		for($i = 0; $i < count($templateTags); $i++){
			$compiled_tags[] = $this->compileTag($templateTags[$i], $nocache);
		}
		
		$content = '';
        for($i = 0; $i < count($text_blocks); $i++){
			if(strpos($text_blocks[$i],'viewliteral')!==false){
				$__m = $text_blocks[$i];
				$content .= $$__m ;
			//注销php代码解析
			/*}elseif(strpos($text_blocks[$i],'viewphp')!==false){
				$__m = $text_blocks[$i];
				$content .= '<?php '. $$__m .' ?>';*/
			}else{
				$content .= $text_blocks[$i];
			}
			if(isset($compiled_tags[$i])){
				$content .= $compiled_tags[$i];
			}
        }
		
		for($i = 0; $i < count($text_blocks); $i++){
			if(strpos($text_blocks[$i],'viewnocache')!==false){
				$__m = $text_blocks[$i];
				$content = str_replace($__m, $$__m, $content);
			}
		}
		
		return $content;
	}
	
	/*
	 * 标签编译
	 *
	 * @param	string	$templateTag	标签数组
	 */
	public function compileTag(string $templateTag,bool $nocache = false): string
	{
		preg_match_all('~((?>[^"\"\'=\s]+))+ | [=]~x', $templateTag, $match);
		$tagStr = $match[0];
        $tagCommand = $tagStr[0];
		$parseVar = $this->compileParseVar($tagStr);
		switch($tagCommand){
			case '_token_':
			case '_TOKEN_':
			case '__token__':
			case '__TOKEN__':
			case '_csrf_':
			case '_CSRF_':
			case '__csrf__':
			case '__CSRF__':
				return $this->compileTonkenTag($tagCommand, $nocache);
			case 'if':
			case 'else':
			case 'elseif':
			case '/if':
				return $this->compileIfTag($tagCommand,$templateTag);
			case 'foreach':
			case 'foreachelse':
			case '/foreach':
				if(stripos($templateTag, 'from') === false && stripos($templateTag, 'item') === false){
					//as模式
					unset($tagStr);
					$tag = trim(str_replace('foreach', '', $templateTag));
					$tagVar = explode('as', $tag, 2);
					//key
					if(isset($tagVar[1]) && stripos($tagVar[1], '=>'))
						$tagVar[1] = explode('=>', $tagVar[1], 2);
					
					if(is_array($tagVar[1])){
						$tagStr[7] = 'key';
						$tagStr[8] = '=';
						$tagStr[9] = substr(trim($tagVar[1][0]), 1);
						$tagStr[6] = substr(trim($tagVar[1][1]), 1);
					}elseif(isset($tagVar[1])){
						$tagStr[6] = substr(trim($tagVar[1]), 1);
					}
					
					$tagStr[3] = trim($tagVar[0]);
					$tagStr[1] = 'from';
					$tagStr[2] = $tagStr[5] = '=';
					$tagStr[4] = 'item';
					$parseVar = $this->compileParseVar($tagStr);
				}
				return $this->compileForeachTag($tagCommand, $parseVar);
			case 'loop':
			case '/loop':
			case 'volist':
			case '/volist':
				return $this->compileLoopTag($tagCommand, $parseVar);
			case 'define':
			case 'defined':
			case '/defined':
			case 'notdefined':
			case '/notdefined':
				return $this->compileDefineTag($tagCommand, $parseVar);
			case 'empty':
			case '/empty':
			case 'notempty':
			case '/notempty':
				return $this->compileEmptyTag($tagCommand, $parseVar);
			case 'include':
				return $this->compileIncludeTag($tagCommand, $parseVar);
			case 'import':
				return $this->compileImportTag($tagCommand, $parseVar);
			default :
				if($tagCommand[0] == '$'){
					return $this->compileVariableTag($templateTag);
				}else{
					$func_list = array_merge(loadConfig('Tplfunc'), ['url', 'assign', 'spider', '_define']);
					if(!$nocache || in_array(strtolower($tagCommand), $func_list)){
						if(in_array(strtolower($tagCommand), $func_list)){
							if($output = $this->systemFrametion($tagCommand, $parseVar)){
								return $output;
							}
						}else{
							return '';
						}
					}else{
						return $this->leftDelimiter . $templateTag . $this->rightDelimiter;
					}
				}
		}
	}
	
	/*
	 * 处理变量标签
	 *
	 * @param	string	$tagStr 待处理的字符串
	 * @param	string	$result 处理后的字符串
	 */
	public function compileVarTag($tagStr): string
	{
		if(empty($tagStr)) return '';
		$result = '';
		$chars = preg_split('/([%|\-|+|*|\/|\(|\)])/',$tagStr, -1, PREG_SPLIT_DELIM_CAPTURE);//运算符
		$parameter = $this->foreachParameter;
		foreach($chars as $tagStr){
			if($tagStr!='' && $tagStr[0] == '$'){
				$_var = [];
				if(stripos($tagStr, '\'')){
					return $tagStr;
				}else{
					$tagStr = str_replace('$','',$tagStr);
					$_var = explode('.',$tagStr);
				}
				
				if(isset($parameter['rule']) && isset($parameter['finger'])){
					$var_block = $parameter['rule'][$parameter['finger']]['block'] ?? '';
					$var_name = $parameter['rule'][$parameter['finger']]['name'] ?? '';
					$var_key = $parameter['rule'][$parameter['finger']]['key'] ?? '';
					$names = $parameter['names'] ?? [];
					$group = $parameter['finger'] ?? '';
				}else{
					$var_block = $var_name = $var_key = $group = '';
					$names = [];
				}
				
				if(count($_var)==1){
					//兼容foreach变量
					if($var_block && ($var_name == $tagStr || $var_key == $tagStr || in_array($tagStr, $names))){
						if($var_name == $tagStr){
							if(isset($this->view_vars['foreach'][$group]['$var_name'])){
								$this->view_vars['foreach'][$group]['$var_name'] = '';
							} 
							$result .= "\$this->view_vars['foreach'][$group]['$var_name']";
						}elseif($var_key == $tagStr){
							if(isset($this->view_vars['foreach'][$group]['$var_key'])){
								$this->view_vars['foreach'][$group]['$var_key'] = '';
							} 
							$result .= "\$this->view_vars['foreach'][$group]['$var_key']";
						}else{
							$list = array_flip($names);
							$g = $list[$tagStr];
							if(isset($this->view_vars['foreach'][$g]['$tagStr'])){
								$this->view_vars['foreach'][$g]['$tagStr'] = '';
							} 
							$result .= "\$this->view_vars['foreach'][$g]['$tagStr']";
						}
					}else{
						if(isset($this->view_vars['$tagStr'])){
							$this->view_vars['$tagStr'] = '';
						} 
						$result .= "\$this->view_vars['$tagStr']";
					}
				}elseif($var_block && ($_var[0] == $var_name || in_array($_var[0], $names))){
					if($_var[0] == $var_name){
						$result .= "\$this->view_vars['foreach'][$group]['$var_name']";
					}else{
						$list = array_flip($names);
						$g = $list[$_var[0]];
						$result .= "\$this->view_vars['foreach'][$g]['".$_var[0]."']";
					}
					
					for($i=1;$i<count($_var);$i++){
						$result .= "['$_var[$i]']";
					}
				}elseif(in_array($_var[0], ['request','server','get','post','session'])){
					$__var = '';
					for($i=1;$i<count($_var);$i++){
						$__var .= "['$_var[$i]']";
					}
					$result .= '$_'. strtoupper($_var[0]) . $__var;
				}elseif(in_array($_var[0], ['define','const'])){
					$result .= $_var[1];
				}else{
					$result .= "\$this->view_vars";
					foreach($_var as $key ){
						$result .= "['$key']";
					}
				}
			}else{
				$result .= $tagStr;
			}
		}
		return $result;
	}
	
	/*
	 * 变量标签编译
	 *
	 * @param	string	$templateTag	标签数组
	 */
	public function compileVariableTag(string $templateTag): string
	{
		$return = false;
		$_tplVar = explode('|',$templateTag);
		if(stripos($_tplVar[0], '_func_fix') != false){
			$_tplVar[0] = str_replace('_func_fix.', '',$_tplVar[0]);
			$return = true;
		}
		$result = $this->compileVarTag($_tplVar[0]);
		$_output = '';
		$cline = '&*&%$##';//将\|转换为此码，最后再转义
		$aline = '&@@#$&&';//
		$_res = str_replace('\|', $cline, $templateTag);
		preg_match_all('/(\|)(([a-zA-Z0-9_])+)(([:])?)/',$_res,$res);//标签
		$res = $res[0];
		foreach($res as $key){
			$_res = str_replace($key,$aline,$_res);//将标签替换
		}
		foreach(explode($aline,$_res) as  $key ){
			$_rea[] = explode(':',str_replace($cline,'|',$key));
		}
		$raw = false;
		for($i=0;$i<count($res);$i++){
			$__tplVar = str_replace(['|',':'],'',$res[$i]);
			if($__tplVar == 'raw'){
				$raw = true;
				continue;
			}
			$___var = '';
			foreach($_rea[$i+1] as $_var){
				$__var = $this->compileVarTag(str_replace(['"',"'"],'',$_var));
				if($__var[0]!='$'){
					$___var .= '"'.$__var.'",';
				}else{
					$___var .= $__var.',';
				}
			}
			$_output .= "\$_tmp = \$this->viewModifier(\"".$__tplVar."\",\$_tmp,". rtrim($___var,',') .");";
		}
		$chars = preg_split('/([%|-|+|*|\/|\(|\)])/',$result, -1, PREG_SPLIT_DELIM_CAPTURE);//运算符
		if($_output == ''){
			if($return){
				return "$result";
			}else{
				$_result = '';
				foreach($chars as $tagStr){
					if($tagStr[0] == '$'){
						$_result .= "isset($tagStr) OR $tagStr = '';";
					}
				}
				return "<?php $_result echo $result; ?>";
			}
		}else{
			$output ='<?php';
			foreach($chars as $tagStr){
				if($tagStr[0] == '$'){
					$output .= " isset($tagStr) OR $tagStr = '';";
				}
			}
			$output .= "\$_tmp = $result;";
			$output .= $_output . ($raw ? "echo \$_tmp; \$_tmp = null; ?>" : "echo htmlentities(\$_tmp); \$_tmp = null; ?>");
			return $output;
		}
	}
	
	/*
	 * token标签编译
	 *
	 * @param	string	$templateTag	标签数组
	 */
	public function compileTonkenTag(string $tagCommand,bool $nocache): string
	{
		if(!$nocache){
			switch($tagCommand){
				case '_token_':
				case '_TOKEN_':
				case '__token__':
				case '__TOKEN__':
					return strtolower($tagCommand) == '__token__' ? '<?php takeToken(); ?>' : '<?php takeToken(false); ?>';
				case '_csrf_':
				case '_CSRF_':
				case '__csrf__':
				case '__CSRF__':
					return strtolower($tagCommand) == '__csrf__' ? '<?php takeToken(true, "csrf"); ?>' : '<?php takeToken(false, "csrf"); ?>';
			}
			
		}else{
			return $this->leftDelimiter . $tagCommand . $this->rightDelimiter;
		}
	}
	
	/*
	 * if标签编译
	 *
	 * @param	string	$templateTag	标签数组
	 */
	public function compileIfTag(string $tagCommand,string $templateTag): string
	{
		if($tagCommand == 'if' || $tagCommand == 'elseif'){
			$_preg_match = "|!==|*|-|+|\/|%|===|==|!=|<>|<<|>>|<=|>=|\&\&|\|\||>|<|\(|\)|\"\"|\'";
			preg_match_all('~((?>[^"\''.$_preg_match.'\s]+))+ | ['.$_preg_match.']~x', $templateTag, $match);
			$tagStr = $match[0];
			array_shift($tagStr);
			$_result = "";
			$result = "";
			$_res = "";
			foreach($tagStr as $key){
					if($key[0] == '$'){
						$_result = $this->compileVarTag($key);
						$result .= "$_result";
						$_res .= "isset($_result) OR $_result='';";
					}elseif($key == "view"){
						$result = '';
					}else{
						$result .= $key;
					}
			}
			$result = '<?php '. ($tagCommand == 'if' ? $_res : '') ." $tagCommand($result): ?>";
			return $result;
		}elseif($tagCommand == 'else' ){
			return '<?php else: ?>';
		}elseif($tagCommand == '/if' ){
			return '<?php endif; ?>';
		}
	}
	
	/*
	 * define和notdefine标签编译
	 *
	 * @param	string	$templateTag	标签数组
	 */
	public function compileDefineTag(string $tagCommand,array $templateTag): string
	{
		if($tagCommand == 'define'){
			$name = $templateTag['name'];
			$value = $templateTag['value'];
			if($value[0] != '$'){
				return "<?php define('$name', '$value'); ?>";
			}else{
				$value = $this->compileVarTag($value);
				return "<?php define('$name', $value); ?>";
			}
		}if($tagCommand == 'defined' || $tagCommand == 'notdefine'){
			$name = $templateTag['name'];
			return $tagCommand == 'defined' ? "<?php if(defined($name)): ?>" : "<?php if(!defined($name)): ?>";
		}elseif($tagCommand == '/defined' || $tagCommand == '/notdefine'){
			return '<?php endif; ?>';
		}
	}
	
	/*
	 * empty和notempty标签编译
	 *
	 * @param	string	$templateTag	标签数组
	 */
	public function compileEmptyTag(string $tagCommand,array $templateTag): string
	{
		if($tagCommand == 'empty' || $tagCommand == 'notempty'){
			$_result = "";
			$result = "";
			if($templateTag['name'][0] != '$'){
				$templateTag['name'] = '$'.$templateTag['name'];
			}
			$_result = $this->compileVarTag($templateTag['name']);
			return $tagCommand == 'empty' ? "<?php if(empty($_result)): ?>" : "<?php if(isset($_result) && !empty($_result)): ?>";
		}elseif($tagCommand == '/empty' || $tagCommand == '/notempty'){
			return '<?php endif; ?>';
		}
	}
	
	/*
	 * foreach标签编译
	 *
	 * @param	string	$templateTag	标签数组
	 */
	public function compileForeachTag(string $tagCommand,array $parseVar): string
	{
		$parameter = $this->foreachParameter;
		if($tagCommand == 'foreach'){
			if($parameter['finger'] > 0){
				$parameter['finger']++;
			}else{
				$parameter['finger']=1;
			}
			$parameter['rule'][$parameter['finger']]['block'] = true;
		}
		$group = $parameter['finger'];
		if($tagCommand == 'foreachelse' || $tagCommand == '/foreach'){
			if($tagCommand == 'foreachelse'){
				$parameter['rule'][$parameter['finger']]['else'] = true;
			}
			$parameter['rule'][$parameter['finger']]['block'] = false;
			$parameter['rule'][$parameter['finger']]['name'] = '';
			$parameter['names'][$parameter['finger']] = '';
		}
		if($tagCommand == 'foreach' ){
			if(empty($parseVar['from'])){
				showTplMsg('shutdown', '模版foreach标签from变量没有声明。');
			}
			$from = $parseVar['from'];
			
			if(empty($parseVar['item'])){
				showTplMsg('shutdown', '模版foreach标签item变量没有声明。');
			}
			$item = $parseVar['item'];
			$parameter['rule'][$parameter['finger']]['name'] = $item;
			$parameter['names'][$parameter['finger']] = $item;
			if(isset($parseVar['key'])){
				$key  = $parseVar['key'];
				if(!preg_match('~^\w+$~', $key)){
					Debug::addMsg('<font color="red">foreach：key必须是变量名（文字字符串）。</font>', 4);
					return false;
				}
				$parameter['rule'][$parameter['finger']]['key'] = $key;
				$key_part = "\$this->view_vars['foreach'][$group]['$key'] => ";
			} else {
				$key = null;
				$key_part ="\$this->view_vars['foreach'][$group]['key'] => ";
			}
			
			$result = '<?php ';
			$result .= (stripos($from, '$') === false ? '' : " if(empty($from)) $from = []; ") . " \$_from$group = $from; \n is_array(\$_from$group) OR settype(\$_from$group, 'array'); \n";
			
			$name = $parseVar['name'];
			if(isset($name)){
				$foreach_props = "\$this->_foreach[$name]";
				$result .= "{$foreach_props} = ['total' => count(\$_from$group), 'iteration' => 0];\n";
				$result .= "if({$foreach_props}['total'] > 0):\n ";
				$result .= "    foreach(\$_from$group as $key_part\$this->view_vars['foreach'][$group]['$item']):\n";
				$result .= "        {$foreach_props}['iteration']++;\n";
			} else {
				$result .= "if(count(\$_from$group)>0):\n";
				$result .= "    foreach(\$_from$group as $key_part \$this->view_vars['foreach'][$group]['$item']):\n";
				
			}
			$result .= '?>';
			$this->foreachParameter = $parameter;
			return $result;
		}else if($tagCommand == 'foreachelse'){
			$this->foreachParameter = $parameter;
			return "<?php endforeach; unset(\$_from$group); unset(\$this->view_vars['foreach'][$group]); else: ?>";
		}else if($tagCommand == '/foreach'){
			$else = $parameter['rule'][$parameter['finger']]['else'];
			$parameter['finger']--;
			unset($parameter['rule'][$parameter['finger']]);
			$this->foreachParameter = $parameter;
			if($else){
				return '<?php endif; ?>';
			}else{
				return "<?php endforeach; endif; unset(\$_from$group); unset(\$this->view_vars['foreach'][$group]); ?>";
			}
		}
	}
	
	/*
	 * include标签编译
	 *
	 * @param	string	$templateTag	标签数组
	 * @param	array	$parseVar	参数数组
	 */
	public function compileIncludeTag(string $tagCommand,array $parseVar): string
	{
		$arg_list = [];
		if(empty($parseVar['file'])){
			Debug::addMsg('<font color="red">include：file变量没有声明。</font>', 4);
			return '';
		}
		$_file = $parseVar['file'];
		$_arg = '';
		foreach($parseVar as $key => $value){
			if($key != 'file'){
				if($value[0] != '$'){
					$arg_list[] = "'$key' => '$value'";
				}else{
					$arg_list[] = "'$key' => $value";
					$_arg .= "if(!isset($value)) $value=''; ";
				}
			}else{
				$include_file = $value;
			}
		}
		if($_file[0] != '$'){
			return  "<?php \$this->viewInclude(array('file' => \"" . $include_file . "\", 'vars' => array(".implode(',', (array)$arg_list).")));?>";
		}else{
			$output  = "<?php if(!isset($_file)) $_file='';$_arg; \$_from = $_file; ";
			$output  .= " \$this->viewInclude(array('file' => \$_from, 'vars' => array(".implode(',', (array)$arg_list)."))); unset(\$_from);?>";
			return $output; 
		}
	}
	
	/*
	 * import标签编译
	 *
	 * @param	string	$templateTag	标签数组
	 * @param	array	$parseVar	参数数组
	 * 参数 file,href,type,basepath
	 */
	public function compileImportTag(string $tagCommand,array $parseVar): string
	{
		if(empty($parseVar['type'])){
			Debug::addMsg('<font color="red">import：type变量没有声明。</font>', 4);
			return '';
		}
		
		$basepath = $parseVar['basepath'] ?? '/Public/';
		$file = $parseVar['file'] ?? $parseVar['href'];
		$file = str_replace('.', '/', $file);
		switch($parseVar['type']){
			case 'css':
				return '<link rel="stylesheet" type="text/css" href="'. $basepath . $file .'.css" />';
			case 'js';
				return '<script type="text/javascript" src="'. $basepath . $file .'.js"></script>';
			default :
				$other = '';
				isset($parseVar['width']) && $other .= ' width="'. $parseVar['width'] .'"';
				isset($parseVar['height']) && $other .= ' height="'. $parseVar['height'] .'"';
				isset($parseVar['alt']) && $other .= ' alt="'. $parseVar['alt'] .'"';
				isset($parseVar['title']) && $other .= ' title="'. $parseVar['title'] .'"';
				return '<img src="'. $basepath . $file .'.'. $parseVar['type'] .'"'. $other .' />';
		}
	}
	
	/*
	 * 解析参数数组
	 *
	 * @param	array	$tagStr	参数数组
	 */
	public function compileParseVar($tagStr)
	{
		$parseVar = [];
		if(is_array($tagStr)){
			foreach($tagStr as $key => $value){
				if($value == '='){
					$parseVar[$tagStr[$key-1]] = $this->compileVarTag($tagStr[$key+1]);
				}
			}
			return $parseVar;		
		}else{
			return $tagStr;
		}
	}
	
	/*
	 * include模板文件
	 *
	 * @param	array	$parse	路径参数
	 */
	public function viewInclude(array $parse): void
	{
		$include_file = str_replace('/', $this->tplSeparate, $parse['file']);
		$templateDir = empty($parse['vars']['templateDir']) ? $this->templateDir : $parse['vars']['templateDir'];
		$this->view_vars = array_merge($this->view_vars, $parse['vars']);
		$this->templateExist($include_file, false);
		echo $this->compileFile($include_file, '', false, $templateDir);
	}
	
	//变量调节器
	public function viewModifier()
	{
		$arg_list = func_get_args();
		$_file = $this->extend.'/Modifier/'.$arg_list[0].'.php';
		if(file_exists($_file)){
			include_once $_file;
			$_func = 'view_modifier_'.$arg_list[0];
			if(function_exists($_func)){
				unset($arg_list[0]);
				$params = array_merge($arg_list);
				return call_user_func_array($_func, $params);
			}
		}else{
			return '';
		}
	}
	
	/*
	 * 框架函数库
	 *
	 * @param	array	$tagStr	标签集合
	 * @param	array	$parseVar	参数数组
	 */
	public function systemFrametion(string $_func,array $parseVar): string
	{
		if(function_exists($_func)){
			$vars = [];
			if(count($parseVar) > 0){
				foreach($parseVar as $k=>$v){
					$var = $v!='' ? ($v[0] == '$' ? $v : '\''.$v.'\'') : '\'\'';
					$vars[] .= '\''. $k .'\'=>'. $var;
				}
			}
			
			if(in_array(strtolower($_func), ['assign'])){
				//需要授权模板变量的
				return "<?php \$_var = [".implode(',', (array)$vars)."]; $_func(\$_var, \$this->view_vars); unset(\$_var); ?>";
			}else{
				return "<?php \$_var = [".implode(',', (array)$vars)."]; $_func(\$_var); unset(\$_var); ?>";
			}
		}else{
			return '';
		}
	}
	
	/*
	 * 设置对象变量
	 *
	 * @param	string	$key	键名
	 * @param	mixed	$value	键值
	 */
	public function setVal(string $key, $value=null): void
	{
		$this->$key = $value;
	}
	
	/*
	 * 设置模板全局变量
	 *
	 * @param	string	$key	键名
	 * @param	mixed	$value	键值
	 */
	public function assignByRef(string $key, $value=null): void
	{
		$this->ref[$key] = $value;
	}
	
	/*
	 * 输出前设置的过滤方法
	 *
	 * @param	string	$funcname	函数名
	 */
	public function registerOutputfilter(string $funcname): void
	{
		$this->outPutFilter[] = $funcname;
	}
	
	/*
	 * 检查模板文件是否存在
	 *
	 * @param	string	$templatePath	模板路径
	 */
	public function templateExist(string $name,bool $single = false): bool
	{
		$exist = false;
		$this->templateName($name, $single);
		$templatePath = $this->templatePath();
		
		//404特殊处理
		if(file_exists($templatePath) || (!empty($this->compilePath) && file_exists($this->compilePath)))
			$exist = true;
		
		//404
		if($name == 'Public/404' && !$exist){
			if(!file_exists($templatePath)){
				$this->templateDir = CANDYROOT;
				$this->templatePath = CANDYROOT . $this->tplName;
				$this->compileDir = RUNTIME . 'Comps/404/';
				if(file_exists($this->templatePath)){
					$exist = true;
				}
			}
		}
		
		//不存在
		if(!$exist && !in_array($name, ['Public/404', 'Public/noAuth'])){
			if(C('LASTPATH') == '/mobile'){
				if(!is_null(C('TPL_PATH'))){
					$lastpath = is_dir(CANDYPATH. C('TPL_PATH') .'/'. C('TPLSTYLE') .'/pc') ? '/pc' : '';
				}else{
					$lastpath = is_dir(C('APP_PATH') .'Views/'. C('TPLSTYLE') .'/pc') ? '/pc' : '';
				}
				
				if(file_exists(dirname($this->templateDir) . $lastpath . '/'. $this->tplName)){
					//重新规划目录
					$this->templateDir = dirname($this->templateDir) . $lastpath;
					$this->compileDir = dirname($this->compileDir) . $lastpath;
					$this->cacheDir = dirname($this->cacheDir) . $lastpath;
					$this->assign('res', dirname(dirname($this->c_res)) . $lastpath .'/Resource/');
					$exist = true;
				}
			}
			
			if(!$exist){
				$this->halt('模板'. $this->tplName .'不存在。');
				
				//模板路径
				$tmpfile = str_replace(CANDYROOT, '/', $this->templatePath);
				if(is_null(C('ISADDON'))){
					Debug::addMsg('使用的模板路径: <b> '. $tmpfile . ' </b>');
				}else{
					Debug::addMsg('使用' . C('APPNAME') . '插件的模板路径: <b>'. $tmpfile . '</b>');
				}
				
				showDebugMsg();
				stop();
			}
		}
		return $exist;
	}
	
	/**
     * 清除定义的变量
     *
     * @param string $tplVar 
     */
    public function assignClear(string $tplVar): void
	{
        if(is_array($tplVar)){
            foreach($tplVar as $curr_var){
                unset($this->view_vars[$curr_var]);
			}
        }else{
            unset($this->view_vars[$tplVar]);
		}
    }
	
	/**
     * 清除所有的变量
     */
    public function assignClearAll(): void
	{
        $this->view_vars = [];
    }
	
	/**
     * 清除定义的变量
     *
     * @param string $tplVar 
     */
    public function apidataClear(string $tplVar): void
	{
        if(is_array($tplVar)){
            foreach($tplVar as $curr_var){
                $this->data2api($curr_var);
			}
        }else{
            $this->data2api($curr_var);
		}
    }
	
	/**
     * 清除所有的变量
     */
    public function apidataClearAll(): void
	{
        $this->data2api();
    }
	
	/*
	 * 对API接口数据处理
	 * @param	string	$key	键名
	 * @param	mixed	$value	键值
	 */
	public function data2api(string $key=null, $value=null,string $call_func=null)
	{
		static $data = [];
		    
		if(is_null($value)){
			if(is_null($key)){
				$data = [];
			}elseif($key == ''){
				return C('APIDATA') ?? $data;
			}else{
				$data[$key] = null;
			}
		}else{
			$data[$key] = ($call_func && function_exists($call_func)) ? $call_func($value) : $value;
		}
	}
	
	/*
	 * 信息提示
	 * @param	string	$msg	错误信息
	 */
	public function halt(string $msg): void
	{
		showTplMsg('halt', $msg);
	}
	
	/*
	 * 析构函数
	 */
	function __destructor(): void
	{
		ob_end_clean();
		$this->view_vars = [];   //模板变量数组
		$this->foreachParameter = [];   //foreach模块参数
		$this->ref = [];   //模板全局变量组
		$this->outPutFilter = ['beforeHtmlShow'];   //模板过来方法
		$this->compilePath = '';
	}
}
